//////////////////////////////////////////////////////////////////////////
//Copyright
//Description:
//	definition for Naive Bayes classifier.
//Comments:
//Revisions:
//	08/2012 Xu, Changhai - Initial version
//////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <iostream>
#include <fstream>
#include <strstream>
#include "../../XShared/XStructure/XColumnVector.h"
#include "../../XShared/XDefinition/XNumber.h"
#include "XNaiveBayesClassifier.h"

using namespace std;
using namespace XNumber;

//////////////////////////////////////////////////////////////////////////
//Description:
//	Train data from a file
//Parameters:
//	strFileName - file name
//Return:
//	True if training is successful
//Comments:
//Revisions:
//	08/2012 Xu, Changhai - Initial version
//////////////////////////////////////////////////////////////////////////
bool XNaiveBayesClassifier::Train(const string strFileName)
{
	ifstream file;

	file.open(strFileName.c_str());
	if (!file.is_open()) return false;

	char strLine[1024];
	string strWord;
	bool IsDelimiter;
	int indexWord;
	int indexCategory;
	int nSampleCount;

	indexWord=0;
	indexCategory=0;
	nSampleCount=0;
	while (file.getline(strLine,1024))
	{
		istrstream strStream(strLine);

		IsDelimiter=false;
		while (strStream>>strWord)
		{
			if (strWord=="^")
			{
				IsDelimiter=true;
			}
			else if (IsDelimiter) //category
			{
				if (!m_mapDistinctCategory.count(strWord))
				{
					m_mapDistinctCategory[strWord]=indexCategory;
					indexCategory++;
				}
				nSampleCount++;
				break;
			}
			else //normal input word
			{
				if (!m_mapDistinctWord.count(strWord))
				{
					m_mapDistinctWord[strWord]=indexWord;
					indexWord++;
				}
			}
		}
	}

	file.clear();
	file.close();

	//read again
	int indexSample;

	m_vectorDistinctWord.Create((int)m_mapDistinctWord.size());
	m_vectorDistinctCategory.Create((int)m_mapDistinctCategory.size());
	m_vectorDistinctCategory.SetElementFlags(0);//each flag stores the number of samples corresponding to the category

	for(map<string, int>::iterator iterator = m_mapDistinctWord.begin(); iterator != m_mapDistinctWord.end(); iterator++) 
	{
		m_vectorDistinctWord.Element(iterator->second)=iterator->first;
	}
	for(map<string, int>::iterator iterator = m_mapDistinctCategory.begin(); iterator != m_mapDistinctCategory.end(); iterator++) 
	{
		m_vectorDistinctCategory.Element(iterator->second)=iterator->first;
	}

	m_matrixFeature.Create((int)m_mapDistinctWord.size(),nSampleCount); //feature matrix for all samples
	m_matrixFeature.SetZero();
	m_vectorCategory.Create(nSampleCount); //categrory vector for all samples
	m_vectorCategory.SetZero();

	file.open(strFileName.c_str());
	if (!file.is_open()) return false;

	indexWord=0;
	indexCategory=0;
	indexSample=0;
	while (file.getline(strLine,1024))
	{
		istrstream strStream(strLine);

		IsDelimiter=false;
		while (strStream>>strWord)
		{
			if (strWord=="^")
			{
				IsDelimiter=true;
			}
			else if (IsDelimiter) //category
			{
				m_vectorCategory.Element(indexSample)=m_mapDistinctCategory[strWord];
				m_vectorDistinctCategory.Flag(m_mapDistinctCategory[strWord])++;
				indexSample++;
				break;
			}
			else //normal input word
			{
				m_matrixFeature.Element(m_mapDistinctWord[strWord],indexSample)=1;
			}
		}
	}

	file.close();

	/*m_vectorFeatureMatrix.Create(m_vectorDistinctCategory.GetSize());
	for (int k=0;k<m_vectorDistinctCategory.GetSize();k++)
	{
		m_vectorFeatureMatrix.Element(k).Create(m_matrixFeature.GetHeight(),m_vectorDistinctCategory.Flag(k));
	}

	/*XVector<int> vectorFeatureIndexPerCategory;
	XColumnVector vectorColumn;

	vectorFeatureIndexPerCategory.Create(m_vectorDistinctCategory.GetSize());
	for (int k=0;k<vectorFeatureIndexPerCategory.GetSize();k++)
	{
		vectorFeatureIndexPerCategory.Element(k)=0;
	}

	for (int j=0;j<m_matrixFeature.GetWidth();j++)
	{
		indexCategory= (int)m_vectorCategory.Element(j);
		m_matrixFeature.GetColumn(j,&vectorColumn);
		m_vectorFeatureMatrix.Element(indexCategory).SetColumn(vectorFeatureIndexPerCategory.Element(indexCategory),vectorColumn);
		vectorFeatureIndexPerCategory.Element(indexCategory)++;
	}*/

	//m_matrixFeature.Print();
	//m_vectorCategory.Print();

	return TrainBinary();
}

//////////////////////////////////////////////////////////////////////////
//Description:
//
//Parameters:
//	None
//Return:
//	None
//Comments:
//Revisions:
//	08/2012 Xu, Changhai - Initial version
//////////////////////////////////////////////////////////////////////////
bool XNaiveBayesClassifier::TrainBinary()
{
	if (m_vectorCategory.GetSize()==0 || m_vectorDistinctCategory.GetSize()==0) return false;
	if (m_vectorCategory.GetSize()!=m_matrixFeature.GetWidth()) return false;

	int nFeatureCountForCategory;
	int nFeatureCountForNotCategory;
	double fMParam=0.001;
	double fLikelihood;

	m_vectorLikelihoodMatrix.Create(m_vectorDistinctCategory.GetSize());
	for (int k=0;k<m_vectorDistinctCategory.GetSize();k++)
	{
		//the first column is the likelihood that the feature is observed when the category is true, 
		//and the second column is the likelihood that the feature is observed when the category is not true.
		m_vectorLikelihoodMatrix.Element(k).Create(m_matrixFeature.GetHeight(),X_TWO);

		for (int i=0;i<m_matrixFeature.GetHeight();i++)
		{
			nFeatureCountForCategory=0;
			nFeatureCountForNotCategory=0;
			for (int j=0;j<m_matrixFeature.GetWidth();j++)
			{
				if ((int)m_vectorCategory.Element(j)==k) //for category k
				{
					if (m_matrixFeature.Element(i,j)>0)
					{
						nFeatureCountForCategory++;
					}
				}
				else
				{
					if (m_matrixFeature.Element(i,j)>0)
					{
						nFeatureCountForNotCategory++;
					}
				}
			}

			fLikelihood=(nFeatureCountForCategory+fMParam*0.5)/(m_vectorDistinctCategory.Flag(k)+fMParam);
			m_vectorLikelihoodMatrix.Element(k).Element(i,0)=fLikelihood;

			fLikelihood=(nFeatureCountForNotCategory+fMParam*0.5)/((m_vectorCategory.GetSize()-m_vectorDistinctCategory.Flag(k))+fMParam);
			m_vectorLikelihoodMatrix.Element(k).Element(i,1)=fLikelihood;
		}
	}
	
	return true;
}

//////////////////////////////////////////////////////////////////////////
//Description:
//	Transform text to a feature vector
//Parameters:
//	strText - input text
//	vectorFeature - feature vector
//Return:
//	True if transform is successful
//Comments:
//Revisions:
//	08/2012 Xu, Changhai - Initial version
//////////////////////////////////////////////////////////////////////////
bool XNaiveBayesClassifier::GetFeatureVector(string strText,XColumnVector* vectorFeature)
{
	if (!vectorFeature) return false;

	string strWord;

	vectorFeature->Create(m_vectorDistinctWord.GetSize());
	vectorFeature->SetZero();
	istrstream strStream(strText.c_str());

	while (strStream>>strWord)
	{
		if (m_mapDistinctWord.count(strWord))
		{
			vectorFeature->Element(m_mapDistinctWord[strWord])=1;
		}
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
//Description:
//	Classify text
//Parameters:
//	strText - input text
//Return:
//	True if classification is successful
//Comments:
//Revisions:
//	08/2012 Xu, Changhai - Initial version
//////////////////////////////////////////////////////////////////////////
bool XNaiveBayesClassifier::Classify(string strText)
{
	if (m_vectorDistinctCategory.GetSize()==0) return false;

	XColumnVector vectorFeature;
	int nScale=1000000;

	if (!GetFeatureVector(strText,&vectorFeature)) return false;

	double fLikelihoodYes,fLikelihoodNo;
	double fLikelihoodRatio,fLikelihoodRatioBest;
	int indexCategoryBest;

	fLikelihoodRatioBest=0;
	indexCategoryBest=-1;
	for (int k=0;k<m_vectorDistinctCategory.GetSize();k++)
	{
		fLikelihoodYes=nScale;
		fLikelihoodNo=nScale;
		for (int i=0;i<vectorFeature.GetSize();i++)
		{
			if (vectorFeature.Element(i)>0)
			{
				fLikelihoodYes*=m_vectorLikelihoodMatrix.Element(k).Element(i,0);
				fLikelihoodNo*=m_vectorLikelihoodMatrix.Element(k).Element(i,1);
			}
		}
		if (fLikelihoodNo>0)
		{
			fLikelihoodRatio=fLikelihoodYes/fLikelihoodNo;
			if (fLikelihoodRatio>fLikelihoodRatioBest)
			{
				fLikelihoodRatioBest=fLikelihoodRatio;
				indexCategoryBest=k;
			}
		}

		cout<<"Category "<<m_vectorDistinctCategory.Element(k)<<":      Yes - "<<fLikelihoodYes<<",    ";
		cout<<"No - "<<fLikelihoodNo<<""<<endl;
	}

	if (indexCategoryBest>-1)
	{
		cout<<"Best category: "<<m_vectorDistinctCategory.Element(indexCategoryBest)<<endl;
	}

	return true;
}